package com.yt.xiao.code.generate.maker;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;

import com.yt.xiao.code.generate.dal.po.JdbcInfo;
import com.yt.xiao.code.generate.dal.po.Table;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.Version;

public class FMMakerImpl implements FileMaker {

    private String encoding = "UTF-8";
    /**
     * 模板路径
     * */
    private File templatePath;
    /**
     * 输出路径
     * */
    private String outputPath;
    /**
     * 企业缩写
     * 必须由全部小写字母和数字组成
     * */
    private String ltdAbbr = "xy";
    /**
     * 模块名称(字母数字组成)<br/>
     * 推荐由小写字母和数字组成
     * */
    private String moduleName = "test";

    /**
     * 作者
     */
    private String author = "xiao";

    /**
     * jdbc信息
     */
    private JdbcInfo jdbcInfo;

    /**
     * web action 拓展名(后缀)
     */
    private String actionSuffix = ".action";

    /**
     * table列表
     */
    private List<Table> tableList;

    public void setEncoding(String encoding) {
        if(encoding!=null && Charset.isSupported(encoding)){
            this.encoding = encoding;
        }
    }

    public void setTemplatePath(String tplPath) throws Exception{
        if(tplPath==null){
            tplPath = "tpl";
        }
        File file = new File(tplPath);
        if(file.isDirectory()){
            this.templatePath = file;
        }else{
            file = new File(this.getClass().getClassLoader().getResource(tplPath).getPath());
            if(file.isDirectory()){
                this.templatePath = file;
            }else{
                throw new Exception("模板路径错误!:"+tplPath+" 不存在或不是一个目录!");
            }
        }
    }

    public void setOutPath(String outPath) {
        //this.outputPath=outPath;
        File path = new File(outPath);
        if(path.isDirectory()){
            this.outputPath=path.getAbsolutePath();
        }else{
            path = new File(this.getClass().getClassLoader().getResource("").getPath()).getParentFile().getParentFile();
            String tp1 = path.getPath()+File.separator+outPath;
            new File(tp1).mkdirs();
            this.outputPath=tp1;
        }
    }

    /**
     * 设置企业缩写, 必须全部小写
     * 必须是小写字母和数字组成
     * @throws Exception 
     * */
    public void setLtdAbbr(String ltdAbbr) {
        if(ltdAbbr!=null&&ltdAbbr.matches("^[a-z]{1}[a-z0-9]{0,}$")){
            this.ltdAbbr = ltdAbbr;
        }else{
            throw new IllegalArgumentException("企业缩写:"+ltdAbbr+" 不是由小写字母和数字组成");
        }
    }
    /**
     * 设置模块名称(字母数字组成)<br/>
     * 推荐由小写字母和数字组成
     * @throws Exception 
     * */
    public void setModuleName(String moduleName) {
        if(moduleName!=null && moduleName.matches("^[a-zA-Z]{1}[a-z0-9A-Z_]{0,}$")){
            this.moduleName = moduleName;
        }else{
            throw new IllegalArgumentException("模块名称:"+moduleName+" 不是由字母和数字组成(推荐:小写字母和数字组成)");
        }
    }

    public final void setActionSuffix(String actionSuffix) {
        if(actionSuffix!=null && actionSuffix.matches("[a-zA-Z]{1}[a-z0-9A-Z]{0,}$")){
            this.actionSuffix = "."+actionSuffix;
        }else{
            throw new IllegalArgumentException("action 拓展名(后缀):"+actionSuffix+" 不是由字母和数字组成(推荐:小写字母和数字组成, 字母开头)");
        }
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public void setJdbcInfo(JdbcInfo jdbcInfo) {
        this.jdbcInfo = jdbcInfo;
    }

    public void setTableList(List<Table> tableList) {
        this.tableList = tableList;
    }

    /**
     * 是否是模板文件需要解析处理
     * */
    public static final boolean isTpl(String filename){
        int lpp=filename.lastIndexOf('.');
        if(lpp==-1||(lpp+1)==filename.length()){
            return false;
        }else{
            String ext = filename.substring(lpp+1);
            if("java".equalsIgnoreCase(ext)){
                return true;
            }else if("ftl".equalsIgnoreCase(ext)){
                return true;
            }else if("sql".equalsIgnoreCase(ext)){
                return true;
            }else if("htm".equalsIgnoreCase(ext)){
                return true;
            }else if("html".equalsIgnoreCase(ext)){
                return true;
            }else if("xml".equalsIgnoreCase(ext)){
                return true;
            }else if("txt".equalsIgnoreCase(ext)){
                return true;
            }else if("properties".equalsIgnoreCase(ext)){
                return true;
            }else{
                return false;
            }
        }
    }

    private Configuration cfg;
    private Map<String, Object> rootMap;

    private void init()throws Exception{
        cfg = new Configuration(new Version("2.3.28"));
        cfg.setDirectoryForTemplateLoading(this.templatePath);
        cfg.setObjectWrapper(new DefaultObjectWrapper(new Version("2.3.28")));
        cfg.setDefaultEncoding(encoding);
        Properties prop = new Properties();
        prop.setProperty("locale", "zh_CN");
        prop.setProperty("number_format", "#.##");
        cfg.setSettings(prop);
        
        rootMap = new HashMap<String, Object>();
        rootMap.put("module", this.moduleName);
        rootMap.put("Module", StringUtils.capitalize(this.moduleName));
        rootMap.put("ltdAbbr", this.ltdAbbr);
        rootMap.put("databaseName", this.jdbcInfo.getDatabasename());
        rootMap.put("jdbcUser", this.jdbcInfo.getUsername());
        rootMap.put("jdbcPwd", this.jdbcInfo.getPassword());
        rootMap.put("jdbcURL", this.jdbcInfo.getUrl());
        rootMap.put("tableList", this.tableList);
        rootMap.put("author", this.author);
        rootMap.put("curTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        rootMap.put("actionSuffix", actionSuffix);
    }

    public void make() throws Exception {
        if(this.tableList==null || tableList.size()<1){
            throw new java.lang.IllegalArgumentException("tableList is emtpy!");
        }
        this.init();
        this.parse(this.templatePath);
    }

    private void parse(File fileDir) throws Exception{
        File[] flist = fileDir.listFiles();
        for(File file : flist){
            if(file.isFile()){
                this.parseFile(file);
            }else if(file.isDirectory()){
                this.parseDir(file);
            }
        }
    }

    private void parseDir(File dir) throws Exception{
        File[] dl = dir.listFiles();
        if(dl.length==0){//空目录
            this.parseFile(dir);
        }else{
            for(File file : dl){
                if(file.isDirectory()){
                    this.parseDir(file);
                }else if(file.isFile()){
                    this.parseFile(file);
                }
            }
        }
    }

    private void parseFile(File file) throws Exception{
        if(file.isFile()){//文件
            if(isTpl(file.getName())==false){//原样复制文件到新目录
                String relativePath = file.getPath().substring(templatePath.getPath().length());
                File targetFile = new File(this.outputPath+this.renamePath(this.renameFile(relativePath)));
                targetFile.getParentFile().mkdirs();
                FileOutputStream fis1 = new FileOutputStream(targetFile);
                FileChannel tc = fis1.getChannel();
                FileInputStream fis2 = new FileInputStream(file);
                FileChannel sc = fis2.getChannel();
                tc.transferFrom(sc, 0, file.length());
                tc.close();
                sc.close();
                fis1.close();
                fis2.close();
            }else{
                if(file.getName().indexOf("${iteratorTable")==-1){//不需要迭代
                    this.processTpl(file, null);
                }else{//需要迭代
                    if(this.tableList!=null){
                        for(Table table : this.tableList){
                            this.processTpl(file, table);
                        }
                    }
                }
            }
        }else{//目录
            String relativePath = file.getPath().substring(templatePath.getPath().length());
            File targetFile = new File(this.outputPath+this.renamePath(this.renameFile(relativePath)));
            targetFile.mkdirs();
        }
    }

    private String renameFile(String filename){
        return filename.replace("${ltdAbbr}", this.ltdAbbr).replace("${module}", this.moduleName);
    }

    private String renamePath(String path){
        return path.replace("${ltdAbbr}", this.ltdAbbr).replace("${module}", this.moduleName).replace("${Module}", StringUtils.capitalize(this.moduleName));
    }

    private void processTpl(File file, Table table) throws Exception{
        String relativePath = file.getPath().substring(this.templatePath.getPath().length());
        final String tplName = relativePath;
        relativePath = this.renamePath(this.renameFile(relativePath));
        if(relativePath.indexOf("${iteratorTable}")!=-1){
            relativePath = relativePath.replace("${iteratorTable}", StringUtils.capitalize(table==null?"":table.getName()));
        }
        if(relativePath.indexOf("${iteratorTable.uncapFirstName}")!=-1){
            relativePath = relativePath.replace("${iteratorTable.uncapFirstName}", StringUtils.uncapitalize(table.getName()));
        }
        if(relativePath.indexOf("${iteratorTable.lowerName}")!=-1){
            relativePath = relativePath.replace("${iteratorTable.lowerName}", table.getLowerName());
        }
        File targetFile = new File(this.outputPath+relativePath);
        targetFile.getParentFile().mkdirs();
        
        Template tpl = cfg.getTemplate(tplName.replace('\\', '/'), encoding);

        rootMap.put("table", table);
        
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), this.encoding));
        tpl.process(rootMap, out);
        out.flush();
        out.close();
        rootMap.remove("table");
        //System.out.println(tpl);
    }
}
